package org.urbansoft.icl.reader;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import org.apache.log4j.Logger;
import org.urbansoft.icl.CashLetter;
import org.urbansoft.icl.ForwardPresentmentBundle;
import org.urbansoft.icl.ICLFile;
import org.urbansoft.icl.ICLItem;
import org.urbansoft.icl.ImageView;
import org.urbansoft.icl.ReturnItem;
import org.urbansoft.icl.record.BundleControlRecord;
import org.urbansoft.icl.record.BundleHeaderRecord;
import org.urbansoft.icl.record.CashLetterControlRecord;
import org.urbansoft.icl.record.CashLetterHeaderRecord;
import org.urbansoft.icl.record.CheckDetailAddendumARecord;
import org.urbansoft.icl.record.CheckDetailAddendumCRecord;
import org.urbansoft.icl.record.CheckDetailRecord;
import org.urbansoft.icl.record.FileControlRecord;
import org.urbansoft.icl.record.FileHeaderRecord;
import org.urbansoft.icl.record.ImageViewAnalysisRecord;
import org.urbansoft.icl.record.ImageViewDataRecord;
import org.urbansoft.icl.record.ImageViewDetailRecord;
import org.urbansoft.icl.record.Record;
import org.urbansoft.icl.record.ReturnAddendumARecord;
import org.urbansoft.icl.record.ReturnAddendumD3501;
import org.urbansoft.icl.record.ReturnAddendumD3502;
import org.urbansoft.icl.record.ReturnAddendumD3503;
import org.urbansoft.icl.record.ReturnAddendumD3504;
import org.urbansoft.icl.record.ReturnAddendumD3505;
import org.urbansoft.icl.record.ReturnAddendumD3506;
import org.urbansoft.icl.record.ReturnAddendumD3598;
import org.urbansoft.icl.record.ReturnAddendumD3599;
import org.urbansoft.icl.record.ReturnRecord;
import org.urbansoft.icl.util.FileEncoding;
import org.urbansoft.icl.util.ICLException;

/**
 * The Class ICLReader.
 */
public class ICLReader {
	private static Logger log = Logger.getLogger(ICLReader.class);
	private ICLFile iclFile;
	private InputStream is;
	private ArrayList<Record> records;
	private int recordPosition;
	private FileEncoding fileEncoding;

	/**
	 * Instantiates a new iCL reader.
	 * 
	 * @param is
	 *            the is
	 */
	public ICLReader(InputStream is, FileEncoding fileEncoding) {
		this.is = is;
		this.fileEncoding = fileEncoding;
	}

	/**
	 * Read icl file.
	 * 
	 * @return the iCL file
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws ICLException
	 *             the ICL exception
	 */
	public ICLFile readICLFile() throws IOException, ICLException {
		/*
		 * read the raw records from the file and put store in an array list.
		 */
		ICLFileRecordReader recordReader = new ICLFileRecordReader(is,
				fileEncoding);
		records = recordReader.read();
		// log.info("***Registros leidos ***");

		/*
		 * go through the list of records and load them into the file
		 */
		iclFile = loadRecords();
		/*
		 * keep our file encoding in the event we want to transform this into an
		 * output file
		 */
		iclFile.setFileEncoding(fileEncoding);

		return iclFile;
	}

	/**
	 * Load records.
	 * 
	 * @return the iCL file
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ICLFile loadRecords() throws ICLException {

		ICLFile iclFile = new ICLFile();

		/*
		 * 01 load the file header. If the first record is not a file header
		 * record then throw an exception
		 */
		if (nextRecordType() == null) {
			/* empty file */
			return null;
		}

		FileHeaderRecord fileHeader = (FileHeaderRecord) getNextRecord();
		if (!fileHeader.recordType.getFieldData().equals("01")) {
			throwException(fileHeader);
		}
		iclFile.setFileHeader(fileHeader);

		/* loop to load cash letters */
		iclFile.setCashLetterList(loadCashLetters());

		/* 99 file control record */
		FileControlRecord fileControl = (FileControlRecord) getNextRecord();
		if (!fileControl.recordType.getFieldData().equals("99")) {
			throwException(fileControl);
		}
		iclFile.setFileControl(fileControl);

		return iclFile;
	}

	/**
	 * Load cash letters.
	 * 
	 * @return the array list< cash letter>
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ArrayList<CashLetter> loadCashLetters() throws ICLException {
		// log.info("Armado cash letter");
		ArrayList<CashLetter> cashLetters = new ArrayList<CashLetter>();

		/* peek ahead and make sure we have a cash letter in the file */
		if (nextRecordType().equals("10") && nextRecordType() != null) {

			/* loop through cash letters until we hit the file control */
			while (!nextRecordType().equals("99") && nextRecordType() != null) {

				CashLetter cashLetter = new CashLetter();
				CashLetterHeaderRecord cashLetterHeader = (CashLetterHeaderRecord) getNextRecord();
				if (!cashLetterHeader.recordType.getFieldData().equals("10")) {
					throwException(cashLetterHeader);
				}
				/* 10 load the cash Letter Header Record. */
				cashLetter.setCashLetterHeader(cashLetterHeader);

				/* load the bundles */
				cashLetter
						.setForwardPresentmentBundleList(loadForwardBundles());
				// cashLetter.setReturnBundleList(loadReturnBundles());

				/* 90 load the cash letter control */
				CashLetterControlRecord cashLetterControl = (CashLetterControlRecord) getNextRecord();
				if (!cashLetterControl.recordType.getFieldData().equals("90")) {
					throwException(cashLetterControl);
				}
				cashLetter.setCashLetterControl(cashLetterControl);

				/* add the letter to our list */
				cashLetters.add(cashLetter);
			}
		}
		return cashLetters;
	}

	/**
	 * Load forward bundles.
	 * 
	 * @return the array list< forward presentment bundle>
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ArrayList<ForwardPresentmentBundle> loadForwardBundles()
			throws ICLException {
		// log.info("Armando Bundle");
		ArrayList<ForwardPresentmentBundle> bundles = new ArrayList<ForwardPresentmentBundle>();

		/* peek ahead and make sure we have a bundle in the file */
		if (nextRecordType().equals("20") && nextRecordType() != null) {

			/* loop through cash letters until we hit the cash letter control */
			while (!nextRecordType().equals("90") && nextRecordType() != null) {

				ForwardPresentmentBundle bundle = new ForwardPresentmentBundle();

				BundleHeaderRecord bundleHeader = (BundleHeaderRecord) getNextRecord();
				if (!bundleHeader.recordType.getFieldData().equals("20")) {
					throwException(bundleHeader);
				}
				/* add the header */
				bundle.setBundleHeader(bundleHeader);
				/* add the items */
				String type = bundleHeader.collectionTypeIndicator
						.getFieldData();
				if (type.equals("01")) {
					bundle.setItemList(loadItems());
				} else if (type.equals("03")) {
					bundle.setItemList(loadReturnItems());
				} else {

				}

				/* add the control */
				BundleControlRecord bundleControl = (BundleControlRecord) getNextRecord();
				if (!bundleControl.recordType.getFieldData().equals("70")) {
					throwException(bundleControl);
				}
				bundle.setBundleControl(bundleControl);

				/* add the bundle to our list */
				bundles.add(bundle);
			}
		}
		return bundles;
	}

	/**
	 * Load items.
	 * 
	 * @return the array list< icl item>
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ArrayList<ICLItem> loadItems() throws ICLException {
		// log.info("Armando Forward Items");
		ArrayList<ICLItem> items = new ArrayList<ICLItem>();
		/* peek ahead and make sure we have an item in the file */
		if (nextRecordType().equals("25") && nextRecordType() != null) {

			/* loop through items until we hit the bundle control */
			while (!nextRecordType().equals("70") && nextRecordType() != null) {
				ICLItem iclItem = new ICLItem();

				CheckDetailRecord checkDetail = (CheckDetailRecord) getNextRecord();
				if (!checkDetail.recordType.getFieldData().equals("25")) {
					throwException(checkDetail);
				}
				iclItem.setCheckDetail(checkDetail);

				ArrayList<CheckDetailAddendumARecord> addendumARecords = new ArrayList<CheckDetailAddendumARecord>();
				while (nextRecordType().equals("26")) {
					/* add the Check Detail Addendum A Records */
					addendumARecords
							.add((CheckDetailAddendumARecord) getNextRecord());
				}
				iclItem.setCheckDetailAddendumAList(addendumARecords);

				ArrayList<CheckDetailAddendumCRecord> addendumCRecords = new ArrayList<CheckDetailAddendumCRecord>();
				while (nextRecordType().equals("28")) {
					/* add the Check Detail Addendum A Records */
					addendumCRecords
							.add((CheckDetailAddendumCRecord) getNextRecord());
				}
				iclItem.setCheckDetailAddendumCList(addendumCRecords);

				/* add image views */
				iclItem.setImageViewList(loadImageViews());

				/* add the item to our list */
				items.add(iclItem);
			}
		}
		return items;
	}

	/**
	 * Load items.
	 * 
	 * @return the array list< icl item>
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ArrayList<ReturnItem> loadReturnItems() throws ICLException {
		// log.info("Armando Returns Items");
		ArrayList<ReturnItem> items = new ArrayList<ReturnItem>();
		/* peek ahead and make sure we have an item in the file */
		if (nextRecordType().equals("31") && nextRecordType() != null) {

			/* loop through items until we hit the bundle control */
			while (!nextRecordType().equals("70") && nextRecordType() != null) {
				ReturnItem iclItem = new ReturnItem();

				ReturnRecord checkDetail = (ReturnRecord) getNextRecord();
				if (!checkDetail.recordType.getFieldData().equals("31")) {
					throwException(checkDetail);
				}
				iclItem.setReturnDetail(checkDetail);

				ArrayList<ReturnAddendumARecord> addendumARecords = new ArrayList<ReturnAddendumARecord>();
				while (nextRecordType().equals("32")) {
					/* add the Check Detail Addendum A Records */
					addendumARecords
							.add((ReturnAddendumARecord) getNextRecord());
				}
				iclItem.setReturnDetailAddendumAList(addendumARecords);

				// leer addendumD records
				while (nextRecordType().equals("35")) {
					/* add the Check Detail Addendum A Records */
					loadAddendumDRecords((Object) getNextRecord(), iclItem);
				}

				/* add image views */
				iclItem.setImageViewList(loadImageViews());

				/* add the item to our list */
				items.add(iclItem);
			}
		}
		return items;
	}

	/**
	 * Load addendum D records
	 * 
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private void loadAddendumDRecords(Object record, ReturnItem returnItem) {
		if (record instanceof ReturnAddendumD3501) {
			returnItem.setReturnAdd3501((ReturnAddendumD3501) record);
		} else if (record instanceof ReturnAddendumD3502) {
			returnItem.setReturnAdd3502((ReturnAddendumD3502) record);
		} else if (record instanceof ReturnAddendumD3503) {
			returnItem.setReturnAdd3503((ReturnAddendumD3503) record);
		} else if (record instanceof ReturnAddendumD3504) {
			returnItem.setReturnAdd3504((ReturnAddendumD3504) record);
		} else if (record instanceof ReturnAddendumD3505) {
			returnItem.setReturnAdd3505((ReturnAddendumD3505) record);
		} else if (record instanceof ReturnAddendumD3506) {
			returnItem.setReturnAdd3506((ReturnAddendumD3506) record);
		} else if (record instanceof ReturnAddendumD3598) {
			ArrayList<ReturnAddendumD3598> returnDetailAddendumD3598List = returnItem
					.getReturnDetailAddendumD3598List();
			returnDetailAddendumD3598List.add((ReturnAddendumD3598) record);
		} else if (record instanceof ReturnAddendumD3599) {
			ArrayList<ReturnAddendumD3599> returnDetailAddendumD3599List = returnItem
					.getReturnDetailAddendumD3599List();
			returnDetailAddendumD3599List.add((ReturnAddendumD3599) record);
		}
	}

	/**
	 * Load image views.
	 * 
	 * @return the array list< image view>
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private ArrayList<ImageView> loadImageViews() throws ICLException {
		// log.info("Armando ImagesView");
		ArrayList<ImageView> imageViews = new ArrayList<ImageView>();
		/* peek ahead and make sure we have an image in the file */
		if (nextRecordType().equals("50") && nextRecordType() != null) {

			/*
			 * loop through items until we hit the next check detail or bundle
			 * control
			 */
			while (!nextRecordType().equals("25")
					&& !nextRecordType().equals("70")
					&& !nextRecordType().equals("31")
					&& nextRecordType() != null) {

				ImageView imageView = new ImageView();

				ImageViewDetailRecord imageDetail = (ImageViewDetailRecord) getNextRecord();
				if (!imageDetail.recordType.getFieldData().equals("50")) {
					throwException(imageDetail);
				}

				/* add the detail */
				imageView.setImageViewDetail(imageDetail);

				/*
				 * add the data - throw exception if it is not there because it
				 * is required to follow a image view detail record
				 */
				ImageViewDataRecord dataRecord = (ImageViewDataRecord) getNextRecord();
				if (!dataRecord.recordType.getFieldData().equals("52")) {
					throwException(dataRecord);
				}
				imageView.setImageViewData(dataRecord);

				/* optionally add analysis */
				if (nextRecordType().equals("54")) {
					imageView
							.setImageViewAnalysis((ImageViewAnalysisRecord) getNextRecord());
				}

				/* add the image view to the list */
				imageViews.add(imageView);
			}
		}

		return imageViews;
	}

	/**
	 * Next record type.
	 * 
	 * @return the string
	 */
	private String nextRecordType() {
		String recType = null;
		try {
			recType = records.get(recordPosition).getFieldDataAtPosition(1);
		} catch (ArrayIndexOutOfBoundsException e) {
			/* there is no next record, just return null */
			log.error(e.getMessage());
		}
		return recType;
	}

	/**
	 * Gets the next record.
	 * 
	 * @return the next record
	 */
	private Record getNextRecord() {
		Record r = null;
		try {
			r = records.get(recordPosition);
			recordPosition++;
		} catch (ArrayIndexOutOfBoundsException e) {
			/* there is no next record */
			log.error(e.getMessage());
		}
		return r;

	}

	/**
	 * Throw exception.
	 * 
	 * @param failureRecord
	 *            the failure record
	 * 
	 * @throws ICLException
	 *             the ICL exception
	 */
	private void throwException(Record failureRecord) throws ICLException {
		StringBuffer buf = new StringBuffer();
		buf.append("Input file does not appear to be the correct format");
		buf.append(System.getProperty("line.separator"));
		buf.append("Offending Record: ");
		buf.append(failureRecord.toString());
		buf.append(System.getProperty("line.separator"));
		buf.append("Record Structure: ");
		buf.append(System.getProperty("line.separator"));
		buf.append(failureRecord.getStructureXML());

		throw new ICLException(buf.toString());
	}

}
